package simpledb.index;

import simpledb.common.Database;
import simpledb.common.DbException;
import simpledb.common.Type;
import simpledb.execution.IndexPredicate;
import simpledb.execution.OpIterator;
import simpledb.storage.DbFileIterator;
import simpledb.storage.Tuple;
import simpledb.storage.TupleDesc;
import simpledb.transaction.TransactionAbortedException;
import simpledb.transaction.TransactionId;

import java.util.NoSuchElementException;

/**
 * BTreeScan 是一个操作符（OpIterator），用于按照指定条件（谓词）顺序读取 B+ 树中的元组（Tuple）。
 *
 * 它可以遍历整个表的所有元组，也可以根据索引谓词只遍历符合条件的元组。
 */
public class BTreeScan implements OpIterator {

    private static final long        serialVersionUID = 1L;

    private boolean                  isOpen           = false; // 表示扫描是否已打开

    private final TransactionId      tid;                     // 所属事务 ID

    private TupleDesc                myTd;                    // 当前扫描返回的元组描述信息（带别名）

    private IndexPredicate           ipred            = null; // 索引匹配条件，若为 null 则遍历所有数据

    private transient DbFileIterator it;                      // 实际的数据迭代器

    private String                   tablename;               // 被扫描的表名称

    private String                   alias;                   // 表的别名，用于构造字段名（如：tableAlias.fieldName）

    /**
     * 创建一个在指定事务中运行的 B+ 树扫描。
     *
     * @param tid 执行该扫描的事务 ID
     * @param tableid 要扫描的表 ID
     * @param tableAlias 表的别名（供解析器使用）
     * @param ipred 索引匹配条件，若为 null，则按排序顺序返回所有元组
     */
    public BTreeScan(TransactionId tid, int tableid, String tableAlias, IndexPredicate ipred) {
        this.tid = tid;
        this.ipred = ipred;
        reset(tableid, tableAlias);
    }

    /**
     * 获取当前扫描所访问的表名称。
     *
     * @return 表的实际名称
     */
    public String getTableName() {
        return this.tablename;
    }

    /**
     * 获取当前扫描所使用的表别名。
     *
     * @return 表的别名
     */
    public String getAlias() {
        return this.alias;
    }

    /**
     * 重置扫描的目标表和别名。
     *
     * @param tableid 要扫描的表 ID
     * @param tableAlias 表的别名
     */
    public void reset(int tableid, String tableAlias) {
        this.isOpen = false;
        this.alias = tableAlias;
        this.tablename = Database.getCatalog().getTableName(tableid);

        if (ipred == null) {
            // 如果没有指定索引谓词，则创建全表扫描的迭代器
            this.it = Database.getCatalog().getDatabaseFile(tableid).iterator(tid);
        } else {
            // 否则使用 B+ 树索引谓词进行扫描
            this.it = ((BTreeFile) Database.getCatalog().getDatabaseFile(tableid)).indexIterator(tid, ipred);
        }

        // 获取原始的 TupleDesc，并为其字段加上表别名
        myTd = Database.getCatalog().getTupleDesc(tableid);
        String[] newNames = new String[myTd.numFields()];
        Type[] newTypes = new Type[myTd.numFields()];
        for (int i = 0; i < myTd.numFields(); i++) {
            String name = myTd.getFieldName(i);
            Type t = myTd.getFieldType(i);

            newNames[i] = tableAlias + "." + name;
            newTypes[i] = t;
        }
        myTd = new TupleDesc(newTypes, newNames);
    }

    /**
     * 使用默认的表名作为别名构建 BTreeScan。
     */
    public BTreeScan(TransactionId tid, int tableid, IndexPredicate ipred) {
        this(tid, tableid, Database.getCatalog().getTableName(tableid), ipred);
    }

    /**
     * 打开扫描，准备开始读取数据。
     */
    public void open() throws DbException, TransactionAbortedException {
        if (isOpen)
            throw new DbException("不能重复打开同一个迭代器");

        it.open();
        isOpen = true;
    }

    /**
     * 返回带有表别名前缀的 TupleDesc。
     * 这在多个表连接时非常有用，避免字段名冲突。
     *
     * @return 带有别名的 TupleDesc
     */
    public TupleDesc getTupleDesc() {
        return myTd;
    }

    /**
     * 检查是否有下一个元组。
     */
    public boolean hasNext() throws TransactionAbortedException, DbException {
        if (!isOpen)
            throw new IllegalStateException("迭代器未打开");

        return it.hasNext();
    }

    /**
     * 获取下一个元组。
     */
    public Tuple next() throws NoSuchElementException, TransactionAbortedException, DbException {
        if (!isOpen)
            throw new IllegalStateException("迭代器未打开");

        return it.next();
    }

    /**
     * 关闭扫描。
     */
    public void close() {
        it.close();
        isOpen = false;
    }

    /**
     * 重置扫描到初始状态，即重新打开。
     */
    public void rewind() throws DbException, NoSuchElementException, TransactionAbortedException {
        close();
        open();
    }
}
